(module bag-interface mzscheme
  
  (require "../private/require.ss")
  (require-class)
  (require-contracts)
  (require-lists)
  
  (provide bag<%> bag?
           bag/c non-empty-bag/c
           bag-of/c non-empty-bag-of/c)
  
  ;; bag<%> : Interface
  ;; The interface for objects of type (Bag Elem) for any type Elem,
  ;; representing bags of any element type.
  (define bag<%>
    (interface ()
      
      ;; (send (Bag Elem) alist) : (List (cons Elem Integer))
      ;; Produces an association list where each element of the bag is
      ;; associated with its count.
      alist
      
      ;; (send (Bag Elem) elements) : (List Elem)
      ;; Produces the elements of the bag (without counts).
      elements
      
      ;; (send (Bag Elem) insert Elem) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver
      ;; and the given element.  If the receiver contained an equivalent
      ;; element, the count of that element is increased by one.  The
      ;; counts for all other elements remain the same.
      insert

      ;; (send (Bag Elem) insert/count Elem Integer) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver
      ;; and the given element.  If the receiver contained an equivalent
      ;; element, the count of that element is increased by the given
      ;; number.  The counts for all other elements remain the same.
      ;; If the count given is not positive, the resulting bag is the
      ;; same as this bag.
      insert/count

      ;; (send (Bag Elem) lookup Elem [(-> T) (Elem -> T)]) : T
      ;; Looks up an element in the receiver.  If an equivalent one is found,
      ;; the success function is applied to it (returns the element by default).
      ;; If not found, the failure thunk is applied (returns #f by default).
      lookup

      ;; (send (Bag Elem) lookup Elem [(-> T) (Elem Integer -> T)]) : T
      ;; Looks up an element in the receiver.  If an equivalent one is found,
      ;; the success function is applied to it and its count(returns the
      ;; element by default).  If not found, the failure thunk is applied
      ;; (returns #f by default).
      lookup/count

      ;; (send (Bag Elem) count Elem) : Integer
      ;; Returns the number of times the given element appeared in the
      ;; given bag.
      count
      
      ;; (send (Bag Elem) remove Elem) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver not
      ;; equivalent to the given element with the same counts.  If the
      ;; given element is present, then its count is reduced by 1.  If
      ;; its count becomes 0, it is not present in the new bag.  If the
      ;; given element is not present, then this produces a bag
      ;; equivalent to the receiver.
      remove

      ;; (send (Bag Elem) remove* Elem ...) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver not
      ;; equivalent to the given elements with the same counts.  If the
      ;; given elements are present, then each's count is reduced by 1.
      ;; If an element's count becomes 0, it is not present in the new bag.
      ;; If the given elements are not present, then this produces a bag
      ;; equivalent to the receiver.
      remove*

      ;; (send (Bag Elem) remove-all Elem) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver not
      ;; equivalent to the given element with the same counts.  If the
      ;; given element is present, then it is not present in the new bag.
      ;; If the given element is not present, then this produces a bag
      ;; equivalent to the receiver.
      remove/all
      
      ;; (send (Bag Elem) remove/count Elem Integer) : (Bag Elem)
      ;; Produces a new bag containing all elements of the receiver not
      ;; equivalent to the given element with the same counts.  If the
      ;; given element is present, then its count is reduced by the given
      ;; amount.  If its count becomes <= 0, it is not present in the new
      ;; bag.  If the given element is not present, then this produces a
      ;; bag equivalent to the receiver.
      remove/count

      ;; (send (Bag Elem) empty?) : Boolean
      ;; Reports whether the given bag is empty.
      empty?
      
      ;; (send (Bag Elem) size) : Integer
      ;; Returns the size of the bag.  Equivalent elements are counted
      ;; separately.
      size
      
      ;; (send (Bag Elem) size/unique) : Integer
      ;; Returns the size of the bag.  Equivalent elements are only counted
      ;; once.
      size/unique
      
      ;; (send (Bag Elem) member? Elem) : Boolean
      ;; Returns whether the given element is a member of the bag.
      member?
      
      ;; (send (Bag Elem) select) : Elem
      ;; Returns a unspecified element from the bag.
      select

      ;; (send (Bag Elem) select/count) : (values Elem PositiveNumber)
      ;; Returns a unspecified element and its count from the bag.
      select/count

      ;; (send Bag Elem) clear) : (Bag Elem)
      ;; Produces an empty bag with the same properties as the receiver.
      clear
      
      ;; (send (Bag Elem) iterator) : (Iterator (cons Elem Integer))
      ;; Constructs an iterator for the elements and counts of a set.
      iterator
      
      ;; (send (Bag Elem) fold (Elem -> T) T) : T
      ;; Builds up a result from each element in the bag.
      fold
      
      ;; (send (Bag Elem) fold/count (Elem Integer -> T) T) : T
      ;; Builds up a result from each element and element count in the
      ;; bag.
      fold/count
      
      ;; (send (Bag Elem) map (Elem -> Elem)) : (Bag Elem)
      ;; Transforms each element in the bag according to the given function.
      map
      
      ;; (send (Bag Elem) map/count (Elem PositiveNumber -> Elem)) : (Bag Elem)
      ;; Like map, but the transformer takes an element count as well.
      map/count
      
      ;; (send (Bag Elem) for-each (Elem -> Void) : Void
      ;; Performs an action for each element in the bag.
      for-each
      
      ;; (send (Bag Elem) for-each (Elem Integer -> Void) : Void
      ;; Performs an action for each element and element count in the bag.
      for-each/count

      ;; (send (Bag Elem) filter (Elem -> Boolean)) : (Bag Elem)
      ;; Returns a new bag that contains all the elements of the given
      ;; bag that pass the given predicate.
      filter

      ;; (send (Bag Elem) filter/count (Elem Integer -> Boolean)) : (Bag Elem)
      ;; Returns a new bag that contains all the elements of the given
      ;; bag that pass the given predicate.  The predicate for this also
      ;; takes the number of times the element occurs.
      filter/count

      ;; (send (Bag Elem) all? (Elem -> Boolean)) : Boolean
      ;; Reports whether all elements of the bag satisfy the predicate.
      all?

      ;; (send (Bag Elem) all?/count (Elem Integer -> Boolean)) : Boolean
      ;; Reports whether all elements of the bag satisfy the predicate.
      ;; The predicate takes both the element and the number of times
      ;; it occurs.
      all?/count

      ;; (send (Bag Elem) any? (Elem -> Boolean)) : Boolean
      ;; Reports whether any elements of the bag satisfy the predicate.
      any?

      ;; (send (Bag Elem) any?/count (Elem Integer -> Boolean)) : Boolean
      ;; Reports whether any elements of the bag satisfy the predicate.
      ;; The predicate takes both the element and the number of times
      ;; it occurs.
      any?/count

      ;; (send (Bag Elem) union (Bag Elem)) : (Bag Elem)
      ;; Returns the union of this bag with the given bag.
      ;; The new bag will have the same properties as this bag.
      union
      
      ;; (send (Bag Elem) intersection (Bag Elem)) : (Bag Elem)
      ;; Returns the intersection of this bag with the given bag.
      ;; The new bag will have the same properties as this bag.
      intersection

      ;; (send (Bag Elem) difference (Bag Elem)) : (Bag Elem)
      ;; Returns the difference of this bag with the given bag.
      ;; The new bag will have the same properties as this bag.
      difference

      ;; (send (Bag Elem) subbag? (Bag Elem)) : Boolean
      ;; Returns whether the given bag contains all the elements
      ;; that this bag contains.  This also means that the given
      ;; bag must contain at least as many occurrences of each
      ;; element as this bag does.
      subbag?

      ;; (send (Bag Elem) equal? (Bag Elem)) : Boolean
      ;; Returns whether the two bags are equal (i.e. have the same
      ;; elements and the same number of occurrences of each element).
      equal?
      
      ))
  
  (define bag/c (is-a?/c bag<%>))
  (define non-empty/c (not/c (lambda (bag) (send bag empty?))))

  (define non-empty-bag/c
    (and/c bag/c non-empty/c))

  (define (bag-of/c elem/c)
    (and/c bag/c
           (lambda (bag)
             (send bag all? (predicate-of elem/c)))))

  (define (non-empty-bag-of/c elem/c)
    (and/c (bag-of/c elem/c) non-empty/c))
  
  ;; bag? : Any -> Boolean
  ;; Reports whether a value is a bag
  (define (bag? v)
    (is-a? v bag<%>))
  
  )
